;	Tested and working but only offline;
;	online the game desyncs and/or crashes when L is pressed to load the save

;	Co-Op Save Hack
.equ				TRAINER_OFFSET, 0x00400000

.long				0xC0DE0000
.long				0x00000000

;-------------------------------;
;	Co-Op Save Hack		;
;-------------------------------;

;	Ensure we are in multiplayer mode
LUI	S1,	0x800A		;
LB	S2,	0x5060	(S1)	;Check mode
BEQZ	S2,			Reset_Data
NOP

ORI	S1,	R0,	0x0001	;Quit if not in multiplayer
BNE	S1,	S2,		Co_Op_Save_Hack_End
NOP

BEQZ	R0,			COSH_In_Multiplayer
NOP

				Reset_Data:
LUI	S1,	%hi(0x80000000 + TRAINER_OFFSET)		;
ORI	S1,	S1,	%lo(0x80001AC0 + TRAINER_OFFSET)	;
ADDIU	S2,	R0,	-0x0001	;
SW	S2,	0x0000	(S1)	;
SW	S2,	0x0004	(S1)	;Reset "hasWarped" and "saveLoaded" variables
				;if single player mode is active
BEQZ	R0,			Co_Op_Save_Hack_End
NOP

				COSH_In_Multiplayer:
;	Check if we loaded the save already
LUI	S1,	%hi(0x80000000 + TRAINER_OFFSET)		;
				;Only load save if it hasn't been loaded
				;This read's purpose is explained by
				;a write to this address which occurs later in the code
LW	S1,	%lo(0x80001AC4 + TRAINER_OFFSET)	(S1)	;
				;[saveLoaded] >= 0?
BGEZ	S1,			No_Load
NOP

LUI	S1,	0x8010		;
LH	S2,	0x5304	(S1)	;
XORI	S2,	S2,	0x0020	;Check L button; load if only L pressed
BNEZ	S2,			No_Load
NOP

;	Get ID of save to use
LUI	S1,	%hi(0x80000000 + TRAINER_OFFSET)		;
LW	S1,	%lo(0x80001AC0 + TRAINER_OFFSET)	(S1)	;
				;[TRAINER_OFFSET + 0x1AC0] is the ID of the save to load,
				;from 1 through 6, to be initalized elsewhere
ADDIU	S1,	S1,	-0x0001	;Subtract 1; ID is now 0 through 5
BLTZL	S1,			Save_ID
OR	S1,	R0,	R0	;Save 0 by default
ADDIU	S2,	S1,	0x0005	;Bounds checking
BGTZL	S2,			Save_ID
OR	S1,	R0,	R0	;Save 0 by default

				Save_ID:
ORI	S2,	R0,	0x000E	;Shifting left by 0xE is like multiplying by 0x4000,
				;the size of saves
SLLV	S5,	S1,	S2	;S5 = flash offset for selected save

LUI	S0,	0xA460		;0xA4600000
LUI	S1,	%hi(TRAINER_OFFSET)				;
ORI	S1,	S1,	%lo(0x00001AC8 + TRAINER_OFFSET)	;
				;TRAINER_OFFSET + 0x1AC8, where the save will be loaded to
LUI	S2,	0x0800		;0x08000000; Flash base address
				;(other saves at save# * 0x4000; 6 in all starting with #0)
ADDU	S2,	S2,	S5	;Add the offset for the selected save
ORI	S3,	R0,	0x0537	;0x00000537; Saves are 0x537 + 1 bytes in size
;ORI	S4,	R0,	0x0002	;0x00000002; this does not need to be written
				;to 0xA4600010 like usual because the exception
				;handler the game initalizes will do this anyway
				;(incidentally this is why this code causes
				;a recursion crash without the semaphore
				;implemented above)

				Wait_Loop:
LW	S5,	0x0010	(S0)	;
ANDI	S5,	S5,	0x0003	;
BNEZ	S5,			Wait_Loop
NOP

LUI	S4,	%hi(0x80000000 + TRAINER_OFFSET)		;
				;This will prevent the exception handler
				;from calling this code again recursively
				;(this code is executed in the exception handler
				;which is triggered when DMA is done,
				;such as by writing to 0xA460000C like in this case)
				;based on a check performed earlier in this code
SW	R0,	%lo(0x80001AC4 + TRAINER_OFFSET)	(S4)	;
				;[saveLoaded] = 0 (-1 means false; 0 means
				;"loaded but not copied to the correct area")
BEQZ	R0,			Co_Op_DMA_Write
NOP
.long				0xDEADC0DE
				Co_Op_DMA_Write:
SW	S1,	0x0000	(S0)	;RAM
SW	S2,	0x0004	(S0)	;Hardware
SW	S3,	0x000C	(S0)	;Size

;	Copy loaded save from back up save area to 0x801E6010
LUI	S1,	%hi(0x80000000 + TRAINER_OFFSET)		;
ORI	S1,	S1,	%lo(0x80001AC8 + TRAINER_OFFSET)	;
LUI	S2,	0x801E		;
ORI	S2,	S2,	0x6010	;
ADDIU	S3,	S1,	0x0538	;Save length

;S1 = src
;S2 = dest
;S3 = should point to after end of src data
				To_Loop:
LW	S0,	0x0000	(S1)	;
SW	S0,	0x0000	(S2)	;
ADDIU	S1,	S1,	0x0004	;
ADDIU	S2,	S2,	0x0004	;

BEQ	S1,	S3,		Mem_Cpy_Done
NOP

BEQZ	R0,			To_Loop
NOP

				Mem_Cpy_Done:

;	Set all levels to be open in 0x801E6010 save area
LUI	S1,	0x801E		;
ORI	S1,	S1,	0x6010	;
ORI	S2,	R0,	0x0007	;Lupus, Juno and Vela (bits 4, 2 and 1 respectively)
LBU	S3,	0x0030	(S1)	;
OR	S2,	S2,	S3	;
SB	S2,	0x0030	(S1)	;Set all characters unlocked
ORI	S2,	R0,	0x7FF9	;All levels but
				;Asteroid (0x0002) and
				;Mizar's Palace (0x0004)
				;(top bit is irrelevant)

LHU	S3,	0x01C0	(S1)	;
OR	S3,	S3,	S2	;
SH	S3,	0x01C0	(S1)	;Unlock levels for Vela

LHU	S3,	0x0236	(S1)	;
OR	S3,	S3,	S2	;
SH	S3,	0x0236	(S1)	;Unlock levels for Juno

LHU	S3,	0x02AC	(S1)	;
OR	S3,	S3,	S2	;
SH	S3,	0x02AC	(S1)	;Unlock levels for Lupus

				;Mizar's Palace must be unlocked in single player

;	Copy relevant data from 0x801E6010 to multiplayer data
LUI	S2,	%hi(0x80000000 + TRAINER_OFFSET)		;
ORI	S3,	R0,	0x0020	;"L pressed"
SH	S3,	%lo(0x80001ABC + TRAINER_OFFSET)	(S2)	;
				;Written to last_keys global

LUI	S1,	0x801E		;
ORI	S2,	S1,	0x6010	;0x801E6010 - Save base
ADDIU	S2,	S2,	0x015C	;Player 1 data
LUI	S3,	0x800F		;
ORI	S3,	S3,	0xED66	;0x800FED66 - Multiplayer data
ORI	S4,	R0,	0x0076	;Single player data length
ORI	S5,	R0,	0x002A	;Multiplayer data length - single player data length
ORI	S6,	R0,	0x0003	;Do this for the first 3 players

				Player_Load:
LH	S7,	0x0000	(S2)	;
SH	S7,	0x0000	(S3)	;
ADDIU	S2,	S2,	0x0002	;
ADDIU	S3,	S3,	0x0002	;
ADDIU	S4,	S4,	-0x0002	;
BGTZ	S4,			Player_Load
NOP
ADDIU	S6,	S6,	-0x0001	;

ORI	S4,	R0,	0x0076	;
ADDU	S3,	S3,	S5	;
BLTZ	S6,			Players_Loaded
NOP
BGTZ	S6,			Player_Load
NOP

ORI	S2,	S1,	0x60F6
BEQZ	R0,			Player_Load
NOP

				Players_Loaded:
;	Set that the save was loaded to multiplayer
LUI	S1,	%hi(0x80000000 + TRAINER_OFFSET)		;
ORI	S2,	R0,	0x0001	;
SW	S2,	%lo(0x80001AC4 + TRAINER_OFFSET)	(S1)	;

;	We don't need this here but it would be inefficient
;	to copy the data back as soon as we load it,
;	especially since we've spent plenty of time sitting in the
;	exception handler as it is
BEQZ	R0,			Co_Op_Save_Hack_End
NOP

				No_Load:
;	Check if save has been loaded (to multiplayer)
;	and if it hasn't, exit
LUI	S1,	%hi(0x80000000 + TRAINER_OFFSET)		;
LW	S1,	%lo(0x80001AC4 + TRAINER_OFFSET)	(S1)	;
ADDIU	S1,	S1,	-0x0001	;
BNEZ	S1,			Co_Op_Save_Hack_End
NOP

;	OR the levels unlocked from the 0x801E6010
;	onto the levels unlocked in the multiplayer data area
LUI	S1,	0x801E		;
LUI	S2,	0x800F		;
ORI	S7,	R0,	0x0003	;Do this for the first 3 players

ORI	S3,	S1,	0x01D0	;
ORI	S4,	S2,	0xEDCA	;

				Level_Update:
LH	S5,	0x0000	(S3)	;
LH	S6,	0x0000	(S4)	;
OR	S5,	S5,	S6	;
SH	S5,	0x0000	(S4)	;
ADDIU	S7,	S7,	-0x0001	;

BLTZ	S7,			Completed_Level_Update
NOP

ADDIU	S4,	S4,	0x00A0	;Offset multiplayer data pointer accordingly

BGTZ	S7,			Normal_Level_Update
NOP

ORI	S3,	S1,	0x014A	;4th player's data is in front of player 1's
BEQZ	R0,			Level_Update
NOP

				Normal_Level_Update:
ADDIU	S3,	S3,	0x0076	;Offset save data pointer accordingly
BEQZ	R0,			Level_Update
NOP

				Completed_Level_Update:
;	OR keys/objects together for all players and store them
LUI	S1,	0x800F		;
ORI	S1,	S1,	0xED66	;Player data array pointer
				;Keys/objects are at +0x66 in the player data
				;Player data is 0xA0 in size
OR	S2,	R0,	R0	;Keys
OR	S3,	R0,	R0	;Objects
ORI	S5,	R0,	0x0004	;4 players

				Keys_Objects_Combine:
LHU	S4,	0x0066	(S1)	;
OR	S2,	S2,	S4	;
LHU	S4,	0x0068	(S1)	;
OR	S3,	S3,	S4	;
ADDIU	S1,	S1,	0x00A0	;
ADDIU	S5,	S5,	-0x0001	;
BNEZ	S5,			Keys_Objects_Combine
NOP

ORI	S5,	R0,	0x0004	;

				Update_Keys_Objects:
ADDIU	S1,	S1,	-0x00A0	;
SH	S2,	0x0066	(S1)	;
SH	S3,	0x0068	(S1)	;
ADDIU	S5,	S5,	-0x0001	;
BNEZ	S5,			Update_Keys_Objects
NOP

				Copy_To_Save_RAM:
;	Copy relevant data from multiplayer data to 0x801E6010
LUI	S1,	0x801E		;
ORI	S2,	S1,	0x6010	;0x801E6010 - Save base
ADDIU	S2,	S2,	0x015C	;Player 1 data
LUI	S3,	0x800F		;
ORI	S3,	S3,	0xED66	;0x800FED66 - Multiplayer data
ORI	S4,	R0,	0x0076	;Single player data length
ORI	S5,	R0,	0x002A	;Multiplayer data length - single player data length
ORI	S6,	R0,	0x0003	;Do this for the first 3 players

				Player_Store:
LH	S7,	0x0000	(S3)	;
SH	S7,	0x0000	(S2)	;
ADDIU	S2,	S2,	0x0002	;
ADDIU	S3,	S3,	0x0002	;
ADDIU	S4,	S4,	-0x0002	;
BGTZ	S4,			Player_Store
NOP
ADDIU	S6,	S6,	-0x0001	;

ORI	S4,	R0,	0x0076	;
ADDU	S3,	S3,	S5	;
BLTZ	S6,			Players_Stored
NOP
BGTZ	S6,			Player_Store
NOP

ORI	S2,	S1,	0x60F6
BEQZ	R0,			Player_Store
NOP

				Players_Stored:
				Co_Op_Save_Hack_End:
JR	RA
NOP

.align				3
.long				0xE0000000
.long				0x00000000
;	End of Co-Op Save Hack
